package com.unknowpupil.eth.service.impl;

import com.unknowpupil.eth.dto.WalletDto;
import com.unknowpupil.eth.enums.ErrorCodeEnum;
import com.unknowpupil.eth.exception.WalletException;
import com.unknowpupil.eth.properties.EthProperties;
import com.unknowpupil.eth.service.EthService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.web3j.crypto.Bip39Wallet;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.utils.Convert;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;

/**
 * @Classname: EthServiceImpl
 * @Author: <a> w10g </a>
 * @CreateDate: Create in 2020/1/3 16:28
 * @Description:
 * @Version: 1.0
 */
@Service
@Slf4j
public class EthServiceImpl implements EthService {
    @Autowired
    private Web3j web3j;
    @Autowired
    private EthProperties ethProperties;

    /**
     * 功能描述: <br>
     * 〈   根据地址获取ETH 余额
     *
     * addressHexString：	String - 要查询余额的地址。
     * defaultBlock：	Number|String -（可选）如果不设置此值，将使用web3.eth.defaultBlock设定的块，否则使用指定的块。
     * callback：	Funciton - （可选）回调函数，用于支持异步的执行方式。
     * 返回值：             *
     * String - 一个包含给定地址的当前余额的BigNumber实例，单位为wei
     * 〉
     *
     * @param: [address]
     * @since: 1.0.0
     * @auther: w10g
     * @return: java.math.BigDecimal
     * @Date: 2020/1/3 19:32
     */
    @Override
    public BigDecimal getBalance(String address) {
        try {
            /**

             */
            BigInteger balanceWei = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send().getBalance();
            BigDecimal balanceEther = Convert.fromWei(balanceWei.toString(), Convert.Unit.ETHER);
            log.info("[ETH 配置] 获取ETH成功 地址={}，余额={}", address, balanceEther);
            return balanceEther;
        } catch (Exception e) {
            log.warn("[ETH 配置] 获取ETH余额出错 地址={}，错误={}", address, e.getMessage());
            throw new WalletException(ErrorCodeEnum.ETH_MONEY_ERROR);
        }
    }

    /**
     * 功能描述: <br>
     * 〈     创建ETH钱包
     * 这是生成一个新的文件公私钥可从这里获取
     * String fileName = WalletUtils.generateNewWalletFile(password, getEth_file());
     * 〉
     *
     * @param: [password]
     * @since: 1.0.0
     * @auther: w10g
     * @return: com.unknowpupil.eth.dto.WalletDto
     * @Date: 2020/1/3 17:45
     */
    @Override
    public WalletDto createWallet(String password) {
        try {
            Bip39Wallet wallet = WalletUtils.generateBip39Wallet(password, ethProperties.getFile());
            String keyStoreKeyFile = ethProperties.getFile().getPath();
            String memorizingWords = wallet.getMnemonic();
            String filename = wallet.getFilename();
            Credentials credentials = WalletUtils.loadBip39Credentials(password,
                    wallet.getMnemonic());
            String address = credentials.getAddress();
            String publicKey = credentials.getEcKeyPair().getPublicKey().toString(16);
            String privateKey = credentials.getEcKeyPair().getPrivateKey().toString(16);
            log.info("[ETH 配置] ETH钱包创建成功 密码={}\n地址={}\n文件名={}\n文件地址={}\n公钥={}\n私钥={}\n助记词={}\n", password, address, filename, keyStoreKeyFile, publicKey, privateKey, memorizingWords);
            return new WalletDto(address, keyStoreKeyFile, privateKey, publicKey, memorizingWords, filename);
        } catch (Exception e) {
            log.error("[ETH 配置] ETH 钱包创建失败 密码={}\n 错误信息={}", password, e.getMessage());

            throw new WalletException(ErrorCodeEnum.CREATE_ETH_WALLET_ERROR);
        }
    }

    @Override
    public WalletDto readWalletBySource(String password, String source) throws Exception {
        try {
            Credentials credentials = WalletUtils.loadCredentials(password, ethProperties.getFile() + "/" + source);
            String address = credentials.getAddress();
            String publicKey = credentials.getEcKeyPair().getPublicKey().toString(16);
            String privateKey = credentials.getEcKeyPair().getPrivateKey().toString(16);
            return  new WalletDto(address, null, privateKey, publicKey, null, null);
        } catch (Exception e) {
            log.error("[ETH 配置] 根据资源获取公私钥错误 ={}",e.getMessage());
            if (StringUtils.containsOnly(e.getMessage(), "Invalid password provided")) {
                throw new WalletException(ErrorCodeEnum.PASSWORD_ERROR);
            }
            throw new WalletException(ErrorCodeEnum.SOURCE_ERROR);
        }
    }


    /**
     * 功能描述: <br>
     * 〈 获取ETH 区块高度〉
     *
     * @param: []
     * @since: 1.0.0
     * @auther: w10g
     * @return: int
     * @Date: 2020/1/3 19:35
     */
    @Override
    public int getBlockHeight() {
        try {
            int ethHeight = web3j.ethBlockNumber().send().getBlockNumber().intValue();
            log.info("[ETH 配置] 获取最新区块高度成功 高度={}", ethHeight);
            return ethHeight;
        } catch (Exception e) {
            log.error("[ETH 配置] 获取最新区块高度失败");
            throw new WalletException(ErrorCodeEnum.GET_ETH_HEIGHT_ERROR);
        }

    }

    /**
     * 功能描述: <br>
     * 〈 根据区块高度处理区块信息 〉
     *
     * @param: [height]
     * @since: 1.0.0
     * @auther: w10g
     * @return: boolean
     * @Date: 2020/1/3 20:55
     */
    @Override
    public boolean parseBlock(long height) throws Exception {

            EthBlock ethBlock = web3j.ethGetBlockByNumber(DefaultBlockParameter.valueOf(BigInteger.valueOf(height)), true).send();
            EthBlock.Block block = ethBlock.getBlock();
            List<EthBlock.TransactionResult> transactionResults = block.getTransactions();

            //遍历区块中所有的交易
            transactionResults.forEach(transactionResult -> {
                EthBlock.TransactionObject object = (EthBlock.TransactionObject) transactionResult.get();
                try {
                    parserTransactionInfo(object);
                } catch (Exception e) {
                    log.info("[ETH 配置]  处理ETH区块信息失败，高度：{}", height);
                    e.printStackTrace();
                }
            });
            log.info("[ETH 配置]  处理ETH区块，高度：{}", height);
            return true;
    }

    @Override
    public void parserTransactionInfo(Object object) throws Exception {

    }
}
